//
// Created by EmFi on 2023/5/21.
//

#include "TransactionalDatabase.h"

TransactionalDatabase::TransactionalDatabase(const string &fn) : IndexedDatabase(fn), transFn(fn + ".trans") {
    if (!this->_open()) {
        if (!this->_create()) {
            LogUtil::error(this->fn, "Transaction create failed");
            exit(1);
        }
        LogUtil::debug(this->fn, "Transaction created");
    } else {
        stringstream ss;
        ss << "Transaction opened - " << "block_size: " << this->transHead.block_size << ", length: " << this->transHead.length;
        LogUtil::debug(this->fn, ss.str());
    }
}

TransactionalDatabase::~TransactionalDatabase() {
    this->_close();
    LogUtil::debug(this->fn, "Transaction closed");
}

bool TransactionalDatabase::_open() {
    // 打开文件
    this->transConn.open(this->transFn, ios::binary | ios::in | ios::out);
    if (!this->transConn.is_open()) {
        return false;
    }

    // 读取文件头
    this->transConn.read((char*)&this->transHead, sizeof(TransHead));
    return true;
}

bool TransactionalDatabase::_create() {
    // 打开文件
    this->transConn.open(this->transFn, ios::binary | ios::out);
    if (!this->transConn.is_open()) {
        return false;
    }

    // 写出文件头
    TransHead newHead;
    this->transHead = newHead;
    this->transConn.write((char*)&this->transHead, sizeof(TransHead));
    this->transConn.flush();

    // 重新打开文件
    this->_close();
    this->_open();
    return true;
}

void TransactionalDatabase::_close() {
    if (this->transConn.is_open()) {
        this->transConn.close();
    }
}

void TransactionalDatabase::record(Operation &op) {
    long long size = sizeof(TransHead);
    this->transConn.seekp(size, ios::beg);
    this->transConn.seekg(size, ios::beg);
    for (int i = 0; i < this->transHead.length; i++) {
        Operation cur;
        this->transConn.read((char*)&cur, sizeof(Operation));
        size += sizeof(Operation);
        size += this->head.block_size * cur.length;
        this->transConn.seekp(size, ios::beg);
        this->transConn.seekg(size, ios::beg);
    }
    this->transConn.write((char*)&op, sizeof(op));
    this->transConn.write((char*)op.values, sizeof(*op.values) * op.length);
    this->transHead.length++;
    this->transConn.seekp(0, ios::beg);
    this->transConn.write((char*)&this->transHead, sizeof(TransHead));
    this->transConn.flush();
}

void TransactionalDatabase::printTransaction() {
    this->transConn.seekg(sizeof(TransHead), ios::beg);
    for (int i = 0; i < this->transHead.length; i++) {
        Operation op;
        this->transConn.read((char*)&op, sizeof(Operation));
        cout << i << "\t" << StringUtil::timeInfoToString(op.time) << "\t" << StringUtil::operationTypeToString(op.type) << "\t" << op.index << "\t" << op.length << endl;
        this->transConn.seekg(sizeof(*op.values) * op.length, ios::cur);
    }
}

int TransactionalDatabase::insert(Point point) {
    int index = IndexedDatabase::insert(point);
    if (index != -1) {
        Operation op{TimeUtil::now(), OperationType::Insert, index, 1, new Point{ point }};
        this->record(op);
    }
    return index;
}

int TransactionalDatabase::insert(list<Point> list) {
    int index = IndexedDatabase::insert(list);
    if (index != -1) {
        auto *points = new Point[list.size()];
        int i = 0;
        for (const auto &it : list) {
            points[i] = it;
            i++;
        }
        Operation op{TimeUtil::now(), OperationType::Insert, index, i, points};
        this->record(op);
    }
    return index;
}

Point TransactionalDatabase::remove(int index) {
    Point removed = IndexedDatabase::remove(index);
    if (!removed.isNull()) {
        Operation op{TimeUtil::now(), OperationType::Delete, index, 1, new Point{ nullPoint }};
        this->record(op);
    }
    return removed;
}

Point TransactionalDatabase::update(int index, Point point) {
    Point before = IndexedDatabase::update(index, point);
    if (!before.isNull()) {
        Operation op{TimeUtil::now(), OperationType::Update, index, 1, new Point{ point }};
        this->record(op);
    }
    return before;
}

bool TransactionalDatabase::rollback(int transIndex) {
    if (transIndex < 0 || transIndex >= this->transHead.length - 1) {
        LogUtil::error(this->fn, "Cannot rollback to " + to_string(transIndex));
        return false;
    }
    IndexedDatabase::_clear();
    this->transConn.seekg(sizeof(TransHead), ios::beg);
    for (int i = 0; i < transIndex + 1; i++) {
        Operation op;
        this->transConn.read((char*)&op, sizeof(Operation));
        op.values = new Point[op.length];
        this->transConn.read((char*)op.values, sizeof(*op.values) * op.length);
        list<Point> list;
        for (int j = 0; j < op.length; j++) {
            list.push_back(op.values[j]);
        }
        switch (op.type) {
            case Insert:
                IndexedDatabase::_insert(list);
                break;
            case Delete:
                IndexedDatabase::_remove(op.index);
                break;
            case Update:
                IndexedDatabase::_update(op.index, op.values[0]);
                break;
            case Select:
                break;
        }
    }
    this->transHead.length = transIndex + 1;
    this->transConn.seekp(0, ios::beg);
    this->transConn.write((char*)&this->transHead, sizeof(TransHead));
    this->transConn.flush();
    LogUtil::rollback(this->fn, transIndex);
    return true;
}

void TransactionalDatabase::clear() {
    this->_close();
    this->_create();
    IndexedDatabase::clear();
}
